package task

import (
	"sort"
	"strconv"
	"strings"
	"time"
)

type TaskType string

const (
	NoRepeat      TaskType = "NoRepeat"
	DailyRepeat   TaskType = "Daily"
	WeeklyRepeat  TaskType = "Weekly"
	MonthlyRepeat TaskType = "Monthly"

	LastDayOfMonth              = 31
	DayBeforeLastDayOfMonth     = 30
	TwoDaysBeforeLastDayOfMonth = 29
)

var (
	Unavailable = time.Date(9999, 12, 31, 0, 0, 0, 0, time.Local)
	OneDay      = time.Hour * 24
)

type Task struct {
	TaskID            string
	TaskName          string
	Type              TaskType
	StartedAt         string // 00:00
	EndedAt           string // 23:59
	WeeklyDays        []int  //1-7
	MonthlyDay        int    //1-31
	ExecutionTimes    int64
	Timezone          *time.Location
	LastExecutionTime time.Time
	NextExecutionTime time.Time
}

func (t *Task) Execute() {
	t.LastExecutionTime = time.Now()
	t.ExecutionTimes++
	now := time.Now()

	t.NextExecutionTime = t.calculateNextExecutionTime(now)
}

func (t *Task) IsInProgress() bool {
	return false
}

// calculateNextExecutionTime 计算下一次任务执行时间
func (t *Task) calculateNextExecutionTime(now time.Time) time.Time {
	startAtHour, _ := strconv.ParseInt(strings.Split(t.StartedAt, ":")[0], 10, 64)
	startAtMinute, _ := strconv.ParseInt(strings.Split(t.StartedAt, ":")[1], 10, 64)
	currentHour := now.Hour()
	currentMinute := now.Minute()
	currentDayOfMonth := now.Day()
	currentMonth := now.Month()
	currentYear := now.Year()
	switch t.Type {
	case NoRepeat:
		return Unavailable
	case DailyRepeat:
		// 当前时间早于预计开始时间
		if int64(currentHour*60+currentMinute) < startAtHour*60+startAtMinute {
			// 今天
			return time.Date(now.Year(), now.Month(), now.Day(), int(startAtHour), int(startAtMinute), 0, 0, t.Timezone)
		} else {
			// 明天
			return time.Date(now.Year(), now.Month(), now.Day(), int(startAtHour), int(startAtMinute), 0, 0, t.Timezone).Add(OneDay)
		}
	case WeeklyRepeat:
		selectedWeekdays := t.WeeklyDays
		sort.SliceStable(selectedWeekdays, func(i, j int) bool {
			return selectedWeekdays[i] < selectedWeekdays[j]
		})
		// selectedWeekdays=[1,5,7]
		//w = 4
		nextExecutionWeekDay := 0            // 5
		currentWeekday := int(now.Weekday()) // 6
		if currentWeekday > selectedWeekdays[len(selectedWeekdays)-1] || currentWeekday < selectedWeekdays[0] {
			nextExecutionWeekDay = selectedWeekdays[0]
		} else {
			for i, weekday := range selectedWeekdays {
				if currentWeekday > weekday {
					continue
				} else if currentWeekday == weekday {
					if int64(currentHour*60+currentMinute) < startAtHour*60+startAtMinute {
						nextExecutionWeekDay = currentWeekday
					} else {
						nextExecutionWeekDay = selectedWeekdays[(i+1)%len(selectedWeekdays)]
					}
					break
				} else { // current < weekday
					nextExecutionWeekDay = weekday
					break
				}
			}
		}
		afterDays := 0
		for i := 0; i < 7; i++ {
			if (currentWeekday+i)%7 == nextExecutionWeekDay {
				afterDays = i
				break
			}
		}
		return time.Date(now.Year(), now.Month(), now.Day(), int(startAtHour), int(startAtMinute), 0, 0, t.Timezone).Add(time.Duration(afterDays) * OneDay)
	case MonthlyRepeat:

		switch t.MonthlyDay {
		case LastDayOfMonth:
			if isLastNDayOfMonth(currentYear, currentMonth, currentDayOfMonth, 1) && int64(currentHour*60+currentMinute) > startAtHour*60+startAtMinute {
				return time.Date(now.Year(), now.Month(), 0, int(startAtHour), int(startAtMinute), 0, 0, t.Timezone).AddDate(0, 2, 0).AddDate(0, 0, -1)
			} else {
				return time.Date(now.Year(), now.Month(), 0, int(startAtHour), int(startAtMinute), 0, 0, t.Timezone).AddDate(0, 1, 0).AddDate(0, 0, -1)
			}
		case DayBeforeLastDayOfMonth:
			if isLastNDayOfMonth(currentYear, currentMonth, currentDayOfMonth, 2) && int64(currentHour*60+currentMinute) > startAtHour*60+startAtMinute {
				return time.Date(now.Year(), now.Month(), 0, int(startAtHour), int(startAtMinute), 0, 0, t.Timezone).AddDate(0, 2, 0).AddDate(0, 0, -1)
			} else {
				return time.Date(now.Year(), now.Month(), 0, int(startAtHour), int(startAtMinute), 0, 0, t.Timezone).AddDate(0, 1, 0).AddDate(0, 0, -1)
			}
		case TwoDaysBeforeLastDayOfMonth:
			if isLastNDayOfMonth(currentYear, currentMonth, currentDayOfMonth, 3) && int64(currentHour*60+currentMinute) > startAtHour*60+startAtMinute {
				return time.Date(now.Year(), now.Month(), 0, int(startAtHour), int(startAtMinute), 0, 0, t.Timezone).AddDate(0, 2, 0).AddDate(0, 0, -1)
			} else {
				return time.Date(now.Year(), now.Month(), 0, int(startAtHour), int(startAtMinute), 0, 0, t.Timezone).AddDate(0, 1, 0).AddDate(0, 0, -1)
			}
		default:
			// today <= 28, 如果过时间那么就下个月，没过就这个月
			if currentDayOfMonth < t.MonthlyDay || (currentDayOfMonth == t.MonthlyDay && int64(currentHour*60+currentMinute) < startAtHour*60+startAtMinute) {
				return time.Date(now.Year(), now.Month(), t.MonthlyDay, int(startAtHour), int(startAtMinute), 0, 0, t.Timezone)
			} else {
				return time.Date(now.Year(), now.Month(), t.MonthlyDay, int(startAtHour), int(startAtMinute), 0, 0, t.Timezone).AddDate(0, 1, 0)
			}
		}
	}
	return Unavailable
}

// isLastNDayOfMonth 某天是否为某月的倒数第N天
func isLastNDayOfMonth(year int, month time.Month, day int, n int) bool {
	maxDaysNumOfMonth := getMonthMaxDays(year, month)
	return day == maxDaysNumOfMonth-n
}

func getMonthMaxDays(year int, month time.Month) int {
	switch month {
	case 1, 3, 5, 7, 8, 10, 12:
		return 31
	case 2:
		day := time.Date(year, time.March, 1, 0, 0, 0, 0, time.UTC)
		day.AddDate(0, 0, -1)
		return day.Day()
	case 4, 6, 9, 11:
		return 30
	}
	return 30
}

func isValidDayInMonth(dayInMonth int64) bool {
	return dayInMonth > 0 && dayInMonth <= 31
}
